#include "ota_http.h"
#include <curl/curl.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include "log/log.h"
#include "json/cJSON.h"
#include "ota_memchunk.h"
static size_t ota_http_writeToMemChunk(char *buffer,
                                       size_t size,
                                       size_t nitems,
                                       void *outstream)
{
    if (ota_memchunk_append(outstream, buffer, size * nitems) < 0)
    {
        return 0;
    }
    return size * nitems;
}
static int ota_http_curl(char *url, curl_write_callback cb, void *userdata)
{
    log_debug("Trying to get %s", url);
    CURL *curl;
    long response_code;
    curl = curl_easy_init();
    if (!curl)
    {
        log_warn("curl_easy_init failed");
        return -1;
    }

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, userdata);

    log_debug("Curl set opt finished");

    if (curl_easy_perform(curl) != CURLE_OK)
    {
        log_warn("curl_easy_perform failed");
        curl_easy_cleanup(curl);
        return -1;
    }

    log_debug("Curl perform finished");

    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
    curl_easy_cleanup(curl);
    if (response_code != 200)
    {
        return -1;
    }
    return 0;
}

static int ota_http_str2int(char *str)
{
    size_t str_len = strlen(str);
    int result = 0;
    for (size_t i = 0; i < str_len; i++)
    {
        result *= 10;
        result += str[i] - '0';
    }
    return result;
}

/**
 * @brief 计算filename的本地md5, 与服务器端的md5是否一致
 *
 * @param filename 本地文件名
 * @param remote_md5 服务器端md5
 * @return int 0 一致，-1 不一致
 */
static int ota_http_md5Check(char *filename, char *remote_md5)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
    // 1. 计算本地md5
    int fd = open(filename, O_RDONLY);
    uint8_t buf[1024];
    uint8_t md5_hash[MD5_DIGEST_LENGTH];
    MD5_CTX ctx;
    if (fd < 0)
    {
        log_warn("Open %s failed", filename);
        return -1;
    }

    MD5_Init(&ctx);

    size_t read_len = read(fd, buf, sizeof(buf));
    while (read_len)
    {
        MD5_Update(&ctx, buf, read_len);
        read_len = read(fd, buf, sizeof(buf));
    }
    close(fd);
    MD5_Final(md5_hash, &ctx);
    log_info("Local md5 calculated");
    // 2. 和远端md5比较
    char md5_str[2 * MD5_DIGEST_LENGTH + 1];
    for (size_t i = 0; i < MD5_DIGEST_LENGTH; i++)
    {
        sprintf(md5_str + 2 * i, "%02x", md5_hash[i]);
    }
    if (memcmp(remote_md5, md5_str, 2 * MD5_DIGEST_LENGTH) != 0)
    {
        log_warn("Remote md5 %s not equal to local md5 %s", remote_md5, md5_str);
        return -1;
    }
    return 0;
#pragma GCC diagnostic pop
}

int ota_http_getFileinfo(Fileinfo *fileinfo)
{
    // 1. 获取http请求
    MemChunk *chunk = ota_memchunk_init();
    if (!chunk)
    {
        return -1;
    }

    if (ota_http_curl(FILEINFO_URL, ota_http_writeToMemChunk, chunk) < 0)
    {
        log_warn("Request to %s failed", FILEINFO_URL);
        ota_memchunk_destroy(chunk);
        return -1;
    }

    // 2. 解析Json信息
    cJSON *root = cJSON_ParseWithLength(chunk->data, chunk->size);
    ota_memchunk_destroy(chunk);
    if (!root)
    {
        log_warn("cJSON_ParseWithLength failed");
        return -1;
    }
    /*
    {
    "filename": "background-1.0.0.jpg",
    "md5": "05aba63b1d820be5522eb9cfd601c317",
    "major": "1",
    "minor": "0",
    "patch": "0"
    }
    */
    cJSON *filename_json = cJSON_GetObjectItem(root, "filename");
    cJSON *md5_json = cJSON_GetObjectItem(root, "md5");
    cJSON *major_json = cJSON_GetObjectItem(root, "major");
    cJSON *minor_json = cJSON_GetObjectItem(root, "minor");
    cJSON *patch_json = cJSON_GetObjectItem(root, "patch");
    if (!cJSON_IsString(filename_json) ||
        !cJSON_IsString(md5_json) ||
        !cJSON_IsString(major_json) ||
        !cJSON_IsString(minor_json) ||
        !cJSON_IsString(patch_json))
    {
        log_warn("Fileinfo json is not valid");
        cJSON_Delete(root);
        return -1;
    }
    fileinfo->filename = strdup(filename_json->valuestring);
    if (!fileinfo->filename)
    {
        cJSON_Delete(root);
        return -1;
    }

    fileinfo->major = ota_http_str2int(major_json->valuestring);
    fileinfo->minor = ota_http_str2int(minor_json->valuestring);
    fileinfo->patch = ota_http_str2int(patch_json->valuestring);
    memcpy(fileinfo->md5_hash, md5_json->valuestring, 2 * MD5_DIGEST_LENGTH + 1);
    cJSON_Delete(root);
    log_info("Succeed to get fileinfo");

    return 0;
}

int ota_http_getFirmware(Fileinfo *fileinfo, char *filepath)
{
    // 下载固件
    FILE *fp = fopen(filepath, "wb");
    char url[1024];
    snprintf(url, sizeof(url), "%s%s", FW_URL_PREFIX, fileinfo->filename);
    if (ota_http_curl(url, (curl_write_callback)fwrite, fp) < 0)
    {
        log_warn("Request to %s failed", url);
        fclose(fp);
        goto FAIL_EXIT;
    }
    fclose(fp);
    // hash验证
    if (ota_http_md5Check(filepath, fileinfo->md5_hash) < 0)
    {
        log_warn("File %s md5 check failed", filepath);
        goto FAIL_EXIT;
    }
    return 0;

FAIL_EXIT:
    unlink(filepath);
    return -1;
}
